// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

part of dart.core;

// Exceptions are thrown either by the VM or from Dart code.

/**
 * 被所有核心库的异常实现的标记接口。
 *
 * [Exception]的目的是向使用者传递失败信息，以便错误能够以编程的方式解决。
 *
 * 它的目的是被捕获，并且应该包含有用的数据字段。
 * 因此，不建议直接用[:new Exception("message"):]来创建[Exception]实例，
 * 除非作为一个开发过程中的临时措施。
 * 一直到库使用真正的exception才算任务完成。
 */
abstract class Exception {
  factory Exception([var message]) => new _Exception(message);
}


/** Default implementation of [Exception] which carries a message. */
class _Exception implements Exception {
  final message;

  _Exception([this.message]);

  String toString() {
    if (message == null) return "Exception";
    return "Exception: $message";
  }
}


/**
 * 当字符串或其它数据不符合预期格式，无法被解析或处理时抛出异常。
 */
class FormatException implements Exception {
  /**
   * 格式错误的描述信息。
   */
  final String message;

  /**
   * 引起错误的输入根源。
   *
   * 通常情况下这是一个字符串，但也可能是其它类型。
   * 如果这是字符串，其中的部分信息可能会被包含在[toString]消息中。
   *
   * 如果被省略或未知，则为`null`。
   */
  final source;

  /**
   * 在[source]中检测到问题的偏移量。
   *
   * 进入[source]后偏移量从0开始，并在出现此异常时标记格式出错。
   * 如果`source`是字符串，这应该是字符串索引，范围是`0 <= offset <= source.length`。
   *
   * 如果输入源是字符串，[toString]可能会输出偏移量位置的行和字符，
   * 并且偏移量也应该包含在字符串中，或字符串末尾。
   *
   * 可能被省略。如果存在，如果可能的话，[source]也应该存在。
   */
  final int offset;

  /**
   * 使用可选参数[message]（错误信息），创建一个新的FormatException。
   *
   * 可选参数还包括：[source]不正确格式的根源，以及[offset]格式中检测到问题的偏移量。
   */
  const FormatException([this.message = "", this.source, this.offset]);

  /**
   * 返回格式异常的描述。
   *
   * 描述永远包含[message]。
   *
   * 如果[source]存在并且是一个字符串，描述将包含（至少一部分）source。
   * 如果还提供了[offset]，source被包含的部分，也将包含offset，并且offset将被标记。
   *
   * 如果source是一个字符串，并且一行在offset之前中断，包含中断位置偏移量的行将被包含，
   * 并且行号也将作为描述的一部分。行和字符的偏移量从1开始。
   */
  String toString() {
    String report = "FormatException";
    if (message != null && "" != message) {
      report = "$report: $message";
    }
    int offset = this.offset;
    if (source is! String) {
      if (offset != null) {
        report += " (at offset $offset)";
      }
      return report;
    }
    if (offset != null && (offset < 0 || offset > source.length)) {
      offset = null;
    }
    // Source is string and offset is null or valid.
    if (offset == null) {
      String source = this.source;
      if (source.length > 78) {
        source = source.substring(0, 75) + "...";
      }
      return "$report\n$source";
    }
    int lineNum = 1;
    int lineStart = 0;
    bool lastWasCR;
    for (int i = 0; i < offset; i++) {
      int char = source.codeUnitAt(i);
      if (char == 0x0a) {
        if (lineStart != i || !lastWasCR) {
          lineNum++;
        }
        lineStart = i + 1;
        lastWasCR = false;
      } else if (char == 0x0d) {
        lineNum++;
        lineStart = i + 1;
        lastWasCR = true;
      }
    }
    if (lineNum > 1) {
      report += " (at line $lineNum, character ${offset - lineStart + 1})\n";
    } else {
      report += " (at character ${offset + 1})\n";
    }
    int lineEnd = source.length;
    for (int i = offset; i < source.length; i++) {
      int char = source.codeUnitAt(i);
      if (char == 0x0a || char == 0x0d) {
        lineEnd = i;
        break;
      }
    }
    int length = lineEnd - lineStart;
    int start = lineStart;
    int end = lineEnd;
    String prefix = "";
    String postfix = "";
    if (length > 78) {
      // Can't show entire line. Try to anchor at the nearest end, if
      // one is within reach.
      int index = offset - lineStart;
      if (index < 75) {
        end = start + 75;
        postfix = "...";
      } else if (end - offset < 75) {
        start = end - 75;
        prefix = "...";
      } else {
        // Neither end is near, just pick an area around the offset.
        start = offset - 36;
        end = offset + 36;
        prefix = postfix = "...";
      }
    }
    String slice = source.substring(start, end);
    int markOffset = offset - start + prefix.length;
    return "$report$prefix$slice$postfix\n${" " * markOffset}^\n";
  }
}

// 当使用0作为除数，进行整除操作的时候，抛出异常。
class IntegerDivisionByZeroException implements Exception {
  const IntegerDivisionByZeroException();
  String toString() => "IntegerDivisionByZeroException";
}
